﻿using ICodeShare.UI.Converters.Common;
using System;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Globalization;
using System.Windows;
using System.Windows.Data;
using System.Windows.Markup;
using ICodeShare.UI.Converters.Helpers;

namespace ICodeShare.UI.Converters
{
    #region IMultiValueConverter
    /// <summary>
    /// 多值多方式转换器，用于对多个值进行不同方式的转换，然后整合输出
    /// </summary>
    /// <example>
    /// 1.在xmal文件引用DateConverter类所在命名空间。
    ///   xmlns:cvts="http://schemas.extended.wpf.com/converters"
    /// 2.在xaml文件中指定Binding值的Converter。
    ///  <MultiConverterGroup>
    ///     <MultiConverterGroupStep>
    ///         <NumberOfFilesConverter/>
    ///         <TotalFileSizeConverter/>
    ///     </MultiConverterGroupStep>
    ///     <MultiConverterGroupStep>
    ///         <FormatConverter FormatString="{0} files with a total size of {1}KB."/>
    ///     </MultiConverterGroupStep>
    ///  </MultiConverterGroup>
    ///  <Label>
    ///     <Label.Content>
    ///       <MultiBinding>
    ///          <MultiBinding.Converter>
    ///            <con:MultiConverterGroup>
    ///               <con:MultiConverterGroupStep>
    ///                     <con:FormatConverter FormatString="{}{0}, {1}"/>
    ///                     <con:FormatConverter FormatString="{}{1}, {0}"/>
    ///               </con:MultiConverterGroupStep>
    ///               <con:MultiConverterGroupStep>
    ///                     <con:FormatConverter FormatString="{}The set of values you entered could be written as [{0}] or [{1}]."/>
    ///               </con:MultiConverterGroupStep>
    ///            </con:MultiConverterGroup>
    ///         </MultiBinding.Converter>
    ///      <Binding Path="Text" ElementName="_textBox8"/>
    ///      <Binding Path="Text" ElementName="_textBox9"/>
    ///     </MultiBinding>
    ///    </Label.Content>
    ///  </Label>
    /// </example>
    [ContentProperty("Steps")]
    [ValueConversion(typeof(object), typeof(object))]
    public sealed class MultiConverterGroup : IMultiValueConverter
    {
        #region  Members 成员变量
        private static readonly ExceptionHelper exceptionHelper = new ExceptionHelper(typeof(MultiConverterGroup));
        private readonly Collection<MultiConverterGroupStep> steps;
        #endregion

        #region Constructors 构造函数
        /// <summary>
        /// Initializes a new instance of the MultiConverterGroup class.
        /// </summary>
        public MultiConverterGroup()
        {
            this.steps = new Collection<MultiConverterGroupStep>();
        }
        #endregion

        #region  Properties 属性
        /// <summary>
        /// Gets the collection of <see cref="MultiConverterGroupStep"/>s in this <c>MultiConverterGroup</c>.
        /// </summary>
        public Collection<MultiConverterGroupStep> Steps
        {
            get { return this.steps; }
        }
        #endregion

        #region  Base Class Overrides 基类方法重写
        /// <summary>
        /// Attempts to convert the specified values.
        /// </summary>
        /// <param name="values">
        /// The values to convert.
        /// </param>
        /// <param name="targetType">
        /// The type of the binding target property.
        /// </param>
        /// <param name="parameter">
        /// The converter parameter to use.
        /// </param>
        /// <param name="culture">
        /// The culture to use in the converter.
        /// </param>
        /// <returns>
        /// A converted value.
        /// </returns>
        public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
        {
            if (this.Steps.Count == 0)
            {
                return DependencyProperty.UnsetValue;
            }

            exceptionHelper.ResolveAndThrowIf(this.Steps[this.Steps.Count - 1].Converters.Count != 1, "FinalStepMustHaveOneConverter");

            foreach (var step in this.Steps)
            {
                exceptionHelper.ResolveAndThrowIf(step.Converters.Count == 0, "EachStepMustHaveAtLeastOneConverter");
                var convertedValues = new object[step.Converters.Count];

                for (var i = 0; i < step.Converters.Count; ++i)
                {
                    convertedValues[i] = step.Converters[i].Convert(values, targetType, parameter, culture);
                }

                values = convertedValues;
            }

            Debug.Assert(values.Length == 1);
            return values[0];
        }

        /// <summary>
        /// Attempts to convert back the specified values.
        /// </summary>
        /// <param name="value">
        /// The value to convert.
        /// </param>
        /// <param name="targetTypes">
        /// The types of the binding target properties.
        /// </param>
        /// <param name="parameter">
        /// The converter parameter to use.
        /// </param>
        /// <param name="culture">
        /// The culture to use in the converter.
        /// </param>
        /// <returns>
        /// Converted values.
        /// </returns>
        public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
        {
            if (this.Steps.Count == 0)
            {
                return null;
            }

            exceptionHelper.ResolveAndThrowIf(this.Steps[this.Steps.Count - 1].Converters.Count != 1, "FinalStepMustHaveOneConverter");
            var stepValues = new object[] { value };

            for (var i = this.Steps.Count - 1; i >= 0; --i)
            {
                var step = this.Steps[i];
                exceptionHelper.ResolveAndThrowIf(step.Converters.Count == 0, "EachStepMustHaveAtLeastOneConverter");
                exceptionHelper.ResolveAndThrowIf(step.Converters.Count != stepValues.Length, "NumberOfConvertersInStepMustEqualNumberOfValuesFromPreviousStep", i + 1, stepValues.Length, i, step.Converters.Count);
                stepValues = step.Converters[0].ConvertBack(stepValues[0], targetTypes, parameter, culture);
            }

            return stepValues;
        }
        #endregion
    }
    #endregion
}
